<?php
/**
 * Note: 实名认证防沉迷
 */

namespace App\Services;

use App\Exceptions\ApiException;
use App\Models\Game\Center\AccountRealName;
use Illuminate\Http\Client\Response;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Http;

class RealNameService
{
    private const API_CHECK = 'https://api.wlc.nppa.gov.cn/idcard/authentication/check';
    private const API_QUERY = 'http://api2.wlc.nppa.gov.cn/idcard/authentication/query';
    private const API_LOGINOUT = 'http://api2.wlc.nppa.gov.cn/behavior/collection/loginout';

    private const MESSAGES = [
        2001 => '身份证号格式校验失败',
        2002 => '实名认证条目已达上限',
        2003 => '无该编码提交的实名认证记录',
        2004 => '编码已经被占用',
    ];

    /**
     * @var \Illuminate\Config\Repository|\Illuminate\Contracts\Foundation\Application|mixed
     */
    private $config;

    public function __construct()
    {
        $this->config = config('services.realname');
    }

    /**
     * 根据本地/测试/生产环境生成对应的AI值
     * @param int $userId
     * @return string
     */
    protected function makeAi(int $userId): string
    {
        return md5(app()->environment() . adminNodeId() . $userId);
    }

    /**
     * 实名认证 检查
     * @param AccountRealName $accountRealName
     * @return AccountRealName
     * @throws ApiException
     */
    public function check(AccountRealName $accountRealName): AccountRealName
    {
        info(__METHOD__, ['玩家进行实名', $accountRealName->user_id]);
        $params['ai'] = $this->makeAi($accountRealName->user_id);
        $params['name'] = $accountRealName->truename;
        $params['idNum'] = $accountRealName->idcard;

        $header['Content-Type'] = 'application/json; charset=utf-8';
        $header['appId'] = $this->config['app_id'];
        $header['bizId'] = $this->config['biz_id'];
        $header['timestamps'] = now()->timestamp * 1000;

        $response = $this->requestPost(self::API_CHECK, $params, $header);

        // if ($response->json('errcode') !== 0) throw new ApiException($response->json('errmsg'));
        if ($response->json('errcode') !== 0) throw new ApiException(self::MESSAGES[$response->json('errcode')] ?? '实名认证失败, 请核对信息');
        if ($response->json('data.result.status') === 2) throw new ApiException('实名认证失败, 请核对信息(1)');

        if ($response->json('data.result.status') === 0) $accountRealName->status = AccountRealName::STATUS_SUCCESS;
        if ($response->json('data.result.status') === 1) $accountRealName->status = AccountRealName::STATUS_WAIT;

        if ($response->json('data.result.pi')) {
            $accountRealName->realname_id = $response->json('data.result.pi');
            $accountRealName->birthday = $this->getBirthday($accountRealName->realname_id);
        }

        if ($accountRealName->status === AccountRealName::STATUS_SUCCESS) {
            if (Carbon::createFromFormat('Ymd', $accountRealName->birthday)->diffInYears(now()) < 18) {
                $accountRealName->status = AccountRealName::STATUS_SUCCESS_UNDER_AGE;
            }
        }
        $accountRealName->is_fake = AccountRealName::FAKE_NO;
        $accountRealName->save();
        return $accountRealName;
    }

    /**
     * 查询
     * @param AccountRealName $accountRealName
     * @return AccountRealName
     * @throws \Exception
     */
    public function query(AccountRealName $accountRealName): AccountRealName
    {
        $params['ai'] = $this->makeAi($accountRealName->user_id);

        $header['appId'] = $this->config['app_id'];
        $header['bizId'] = $this->config['biz_id'];
        $header['timestamps'] = now()->timestamp * 1000;

        $response = $this->requestGet(self::API_QUERY."?ai=".$params['ai'], $params, $header);

        if ($response->json('errcode') !== 0) $accountRealName->status = AccountRealName::STATUS_FAIL;
        if ($response->json('data.result.status') === 2) $accountRealName->status = AccountRealName::STATUS_FAIL;

        if ($response->json('data.result.status') === 0) $accountRealName->status = AccountRealName::STATUS_SUCCESS;
        if ($response->json('data.result.status') === 1) $accountRealName->status = AccountRealName::STATUS_WAIT;

        if ($response->json('data.result.pi')) {
            $accountRealName->realname_id = $response->json('data.result.pi');
            $accountRealName->birthday = $this->getBirthday($accountRealName->realname_id);
        }

        if ($accountRealName->status === AccountRealName::STATUS_SUCCESS) {
            if (Carbon::createFromFormat('Ymd', $accountRealName->birthday)->diffInYears(now()) < 18) {
                $accountRealName->status = AccountRealName::STATUS_SUCCESS_UNDER_AGE;
            }
        }
        $accountRealName->is_fake = AccountRealName::FAKE_NO;
        $accountRealName->save();
        return $accountRealName;
    }

    /**
     * 数据上报
     * @return bool
     * @throws \Exception
     */
    public function loginout(array $collections)
    {
        $params['collections'] = $collections;

        $header['Content-Type'] = 'application/json; charset=utf-8';
        $header['appId'] = $this->config['app_id'];
        $header['bizId'] = $this->config['biz_id'];
        $header['timestamps'] = now()->timestamp * 1000;

        $response = $this->requestPost(self::API_LOGINOUT, $params, $header);

        if ($response->json('errcode') !== 0) throw new \Exception($response->json('errmsg'));

        return true;
    }

    /**
     * 通过返回的pi获取生日信息
     * @param string $pi
     * @return int
     */
    private function getBirthday(string $pi): int
    {
        $birthday = substr($pi, 0, 6);
        return base_convert($birthday, 26, 10);
    }

    /**
     * 报文加密
     * @param array $body
     * @return string
     */
    protected function signData(array $body): string
    {
        $secretKey = $this->config['secret'];
        $key = hex2bin($secretKey);
        $cipher = "aes-128-gcm";
        $ivlen = openssl_cipher_iv_length($cipher);
        $iv = openssl_random_pseudo_bytes($ivlen);
        $encrypt = openssl_encrypt(json_encode($body), $cipher, $key, OPENSSL_RAW_DATA, $iv, $tag);
        return base64_encode(($iv . $encrypt . $tag));
    }

    /**
     * 接口签名
     * @param array $bodyData
     * @param array $queryParams
     * @param array $header
     * @return false|string
     */
    protected function makeSign(array $bodyData, array $queryParams, array $header)
    {
        $secretKey = $this->config['secret'];
        unset($header['sign'], $header['Content-Type']);

        if (!empty($bodyData)) {
            $encryptedBody = json_encode($bodyData);
        } else {
            $encryptedBody = '';
        }

        $sys_params = $header;
        $final_params = array_merge($sys_params, $queryParams);
        ksort($final_params);

        $str_params = '';
        foreach ($final_params as $k => $v) {
            $str_params .= $k . $v;
        }
        $str_params .= $encryptedBody;
        $str_params = $secretKey . $str_params;
        return hash('sha256', $str_params);
    }

    /**
     * 发送post请求
     * @param string $url
     * @param array $data
     * @param array $header
     * @return Response
     * @throws \Exception
     */
    protected function requestGet(string $url, array $data, array $header): Response
    {
        $header['sign'] = $this->makeSign([], $data, $header);

        // 设置timeout之后, 如果超时会直接抛出异常, 不到 $response->throw, 这里放入try
        try {
            info(__METHOD__, [$url, $header, $data]);
            $response = Http::retry(1, 300)->timeout(5)->withHeaders($header)->get($url);
            $response->throw();
            info(__METHOD__, [$response]);
        } catch (\Exception $e) {
            report($e);
            throw new \Exception("实名校验正在维护中，请稍候重试");
        }
        return $response;
    }

    /**
     * @param string $url
     * @param array $data
     * @param array $header
     * @return Response
     * @throws \Exception
     */
    protected function requestPost(string $url, array $data, array $header): Response
    {
        $body = ['data' => $this->signData($data)];
        $header['sign'] = $this->makeSign($body, [], $header);

        // 设置timeout之后, 如果超时会直接抛出异常, 不到 $response->throw, 这里放入try
        try {
            info(__METHOD__, [$url, $header, $data, $body]);
            $response = Http::retry(1, 300)->timeout(20)->withHeaders($header)->withOptions(['verify' => false])->post($url, $body);
            $response->throw();
            info(__METHOD__, [$response]);
        } catch (\Exception $e) {
            report($e);
            throw new ApiException("实名校验正在维护中，请稍候重试");
        }
        return $response;
    }
}
